/*
** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com)
**
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of version 2 of the GNU Library General
** Public License as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Library General Public License for more details.  To obtain a
** copy of the GNU Library General Public License, write to the Free
** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
** Any permitted reproduction of these routines, in whole or in part,
** must bear this legend.
**
**
** memguard.c
**
** memory allocation wrapper routines
**
** NOTE: based on code (c) 1998 the Retrocade group
** $Id: memguard.c,v 1.2 2001/04/27 14:37:11 neil Exp $
*/

#include <noftypes.h>
#include <memguard.h>

/* undefine macro definitions, so we get real calls */
#undef malloc
#undef free
#undef strdup

#include <string.h>
#include <stdlib.h>
#include <log.h>

/* Maximum number of allocated blocks at any one time */
#define MAX_BLOCKS 4096

/* Memory block structure */
typedef struct memblock_s
{
    void* block_addr;
    int block_size;
    char* file_name;
    int line_num;
} memblock_t;

/* debugging flag */
bool mem_debug = true;

#ifdef NOFRENDO_DEBUG

static int mem_blockcount = 0; /* allocated block count */
static memblock_t* mem_record = NULL;

#define GUARD_STRING "GgUuAaRrDdSsTtRrIiNnGgBbLlOoCcKk"
#define GUARD_LENGTH 256 /* before and after allocated block */

/*
** Check the memory guard to make sure out of bounds writes have not
** occurred.
*/
static int mem_checkguardblock(void* data, int guard_size)
{
    char *check, *block;
    int i, alloc_size;

    /* get the original pointer */
    block = ((char*)data) - guard_size;

    /* get the size */
    alloc_size = *((uint32*)block);
    block += 4;

    /* check leading guard string */
    check = GUARD_STRING;
    for (i = sizeof(uint32); i < guard_size; i++)
    {
        /* wrap */
        if ('\0' == *check)
            check = GUARD_STRING;

        if (*block++ != *check++)
            return -1;
    }

    /* check end of block */
    check = GUARD_STRING;
    block = ((char*)data) + alloc_size;
    for (i = 0; i < guard_size; i++)
    {
        /* wrap */
        if ('\0' == *check)
            check = GUARD_STRING;
        if (*block++ != *check++)
            return -1;
    }

    /* we're okay! */
    return 0;
}

/* free a guard block */
static void mem_freeguardblock(void* data, int guard_size)
{
    char* orig = ((char*)data) - guard_size;

    free(orig);
}

/* allocate memory, guarding with a guard block in front and behind */
static void* mem_guardalloc(int alloc_size, int guard_size)
{
    void* orig;
    char *block, *check;
    uint32* ptr;
    int i;

    /* pad it up to a 32-bit multiple */
    alloc_size = (alloc_size + 3) & ~3;

    /* allocate memory */
    orig = malloc(alloc_size + (guard_size * 2));
    if (NULL == orig)
        return NULL;

    block = (char*)orig;

    /* get it to the pointer we will actually return */
    orig = (void*)((char*)orig + guard_size);

    /* trash it all */
    ptr = (uint32*)orig;
    for (i = alloc_size / 4; i; i--)
        *ptr++ = 0xDEADBEEF;

    /* store the size of the newly allocated block*/
    *((uint32*)block) = alloc_size;
    block += 4;

    /* put guard string at beginning of block */
    check = GUARD_STRING;
    for (i = sizeof(uint32); i < guard_size; i++)
    {
        /* wrap */
        if ('\0' == *check)
            check = GUARD_STRING;

        *block++ = *check++;
    }

    /* put at end of block */
    check = GUARD_STRING;
    block = (char*)orig + alloc_size;
    for (i = 0; i < guard_size; i++)
    {
        /* wrap */
        if ('\0' == *check)
            check = GUARD_STRING;

        *block++ = *check++;
    }

    return orig;
}

/* Free up the space used by the memory block manager */
void mem_cleanup(void)
{
    if (mem_record)
    {
        free(mem_record);
        mem_record = NULL;
    }
}

/* Allocate a bunch of memory to keep track of all memory blocks */
static void mem_init(void)
{
    mem_cleanup();

    mem_blockcount = 0;

    mem_record = malloc(MAX_BLOCKS * sizeof(memblock_t));
    ASSERT(mem_record);
    memset(mem_record, 0, MAX_BLOCKS * sizeof(memblock_t));
}

/* add a block of memory to the master record */
static void mem_addblock(void* data, int block_size, char* file, int line)
{
    int i;

    for (i = 0; i < MAX_BLOCKS; i++)
    {
        if (NULL == mem_record[i].block_addr)
        {
            mem_record[i].block_addr = data;
            mem_record[i].block_size = block_size;
            mem_record[i].file_name = file;
            mem_record[i].line_num = line;
            return;
        }
    }

    ASSERT_MSG("out of memory blocks.");
}

/* find an entry in the block record and delete it */
static void mem_deleteblock(void* data, char* file, int line)
{
    int i;
    char fail[256];

    for (i = 0; i < MAX_BLOCKS; i++)
    {
        if (data == mem_record[i].block_addr)
        {
            if (mem_checkguardblock(mem_record[i].block_addr, GUARD_LENGTH))
            {
                sprintf(fail, "mem_deleteblock 0x%08X at line %d of %s -- block corrupt", (uint32)data, line, file);
                ASSERT_MSG(fail);
            }

            memset(&mem_record[i], 0, sizeof(memblock_t));
            return;
        }
    }

    sprintf(fail, "mem_deleteblock 0x%08X at line %d of %s -- block not found", (uint32)data, line, file);
    ASSERT_MSG(fail);
}
#endif /* NOFRENDO_DEBUG */

/* debugger-friendly versions of calls */
#ifdef NOFRENDO_DEBUG

/* allocates memory and clears it */
void* _my_malloc(int size, char* file, int line)
{
    void* temp;
    char fail[256];

    if (NULL == mem_record && false != mem_debug)
        mem_init();

    if (false != mem_debug)
        temp = mem_guardalloc(size, GUARD_LENGTH);
    else
        temp = malloc(size);

    printf("Malloc: %d at %s:%d\n", size, file, line);
    if (NULL == temp)
    {
        sprintf(fail, "malloc: out of memory at line %d of %s.  block size: %d\n", line, file, size);
        ASSERT_MSG(fail);
    }

    if (false != mem_debug)
        mem_addblock(temp, size, file, line);

    mem_blockcount++;

    return temp;
}

/* free a pointer allocated with my_malloc */
void _my_free(void** data, char* file, int line)
{
    char fail[256];

    if (NULL == data || NULL == *data)
    {
        sprintf(fail, "free: attempted to free NULL pointer at line %d of %s\n", line, file);
        ASSERT_MSG(fail);
    }

    /* if this is true, we are in REAL trouble */
    if (0 == mem_blockcount)
    {
        ASSERT_MSG("free: attempted to free memory when no blocks available");
    }

    mem_blockcount--; /* dec our block count */

    if (false != mem_debug)
    {
        mem_deleteblock(*data, file, line);
        mem_freeguardblock(*data, GUARD_LENGTH);
    }
    else
    {
        free(*data);
    }

    *data = NULL; /* NULL our source */
}

char* _my_strdup(const char* string, char* file, int line)
{
    char* temp;

    if (NULL == string)
        return NULL;

    temp = (char*)_my_malloc(strlen(string) + 1, file, line);
    if (NULL == temp)
        return NULL;

    strcpy(temp, string);

    return temp;
}

#else /* !NOFRENDO_DEBUG */

/* allocates memory and clears it */
void* _my_malloc(int size)
{
    void* temp;
    char fail[256];

    temp = malloc(size);

    if (NULL == temp)
    {
        sprintf(fail, "malloc: out of memory.  block size: %d\n", size);
        ASSERT_MSG(fail);
    }

    return temp;
}

/* free a pointer allocated with my_malloc */
void _my_free(void** data)
{
    char fail[256];

    if (NULL == data || NULL == *data)
    {
        sprintf(fail, "free: attempted to free NULL pointer.\n");
        ASSERT_MSG(fail);
    }

    free(*data);
    *data = NULL; /* NULL our source */
}

char* _my_strdup(const char* string)
{
    char* temp;

    if (NULL == string)
        return NULL;

    /* will ASSERT for us */
    temp = (char*)_my_malloc(strlen(string) + 1);
    if (NULL == temp)
        return NULL;

    strcpy(temp, string);

    return temp;
}

#endif /* !NOFRENDO_DEBUG */

/* check for orphaned memory handles */
void mem_checkleaks(void)
{
#ifdef NOFRENDO_DEBUG
    int i;

    if (false == mem_debug || NULL == mem_record)
        return;

    if (mem_blockcount)
    {
        log_printf("memory leak - %d unfreed block%s\n\n", mem_blockcount, mem_blockcount == 1 ? "" : "s");

        for (i = 0; i < MAX_BLOCKS; i++)
        {
            if (mem_record[i].block_addr)
            {
                log_printf("addr: 0x%08X, size: %d, line %d of %s%s\n",
                           (uint32)mem_record[i].block_addr,
                           mem_record[i].block_size,
                           mem_record[i].line_num,
                           mem_record[i].file_name,
                           (mem_checkguardblock(mem_record[i].block_addr, GUARD_LENGTH)) ? " -- block corrupt" : "");
            }
        }
    }
    else
        log_printf("no memory leaks\n");
#endif /* NOFRENDO_DEBUG */
}

void mem_checkblocks(void)
{
#ifdef NOFRENDO_DEBUG
    int i;

    if (false == mem_debug || NULL == mem_record)
        return;

    for (i = 0; i < MAX_BLOCKS; i++)
    {
        if (mem_record[i].block_addr)
        {
            if (mem_checkguardblock(mem_record[i].block_addr, GUARD_LENGTH))
            {
                log_printf("addr: 0x%08X, size: %d, line %d of %s -- block corrupt\n",
                           (uint32)mem_record[i].block_addr,
                           mem_record[i].block_size,
                           mem_record[i].line_num,
                           mem_record[i].file_name);
            }
        }
    }
#endif /* NOFRENDO_DEBUG */
}

/*
** $Log: memguard.c,v $
** Revision 1.2  2001/04/27 14:37:11  neil
** wheeee
**
** Revision 1.1.1.1  2001/04/27 07:03:54  neil
** initial
**
** Revision 1.23  2000/11/24 21:42:48  matt
** vc complaints
**
** Revision 1.22  2000/11/21 13:27:30  matt
** trash all newly allocated memory
**
** Revision 1.21  2000/11/21 13:22:30  matt
** memory guard shouldn't zero memory for us
**
** Revision 1.20  2000/10/28 14:01:53  matt
** memguard.h was being included in the wrong place
**
** Revision 1.19  2000/10/26 22:48:33  matt
** strdup'ing a NULL ptr returns NULL
**
** Revision 1.18  2000/10/25 13:41:29  matt
** added strdup
**
** Revision 1.17  2000/10/10 13:58:13  matt
** stroustrup squeezing his way in the door
**
** Revision 1.16  2000/10/10 13:03:54  matt
** Mr. Clean makes a guest appearance
**
** Revision 1.15  2000/09/18 02:06:48  matt
** -pedantic is your friend
**
** Revision 1.14  2000/08/11 01:45:48  matt
** hearing about no corrupt blocks every 10 seconds really was annoying
**
** Revision 1.13  2000/07/31 04:28:46  matt
** one million cleanups
**
** Revision 1.12  2000/07/24 04:31:07  matt
** mem_checkblocks now gives feedback
**
** Revision 1.11  2000/07/06 17:20:52  matt
** block manager space itself wasn't being freed - d'oh!
**
** Revision 1.10  2000/07/06 17:15:43  matt
** false isn't NULL, Neil... =)
**
** Revision 1.9  2000/07/05 23:10:01  neil
** It's a shame if the memguard segfaults
**
** Revision 1.8  2000/06/26 04:54:48  matt
** simplified and made more robust
**
** Revision 1.7  2000/06/12 01:11:41  matt
** cleaned up some error output for win32
**
** Revision 1.6  2000/06/09 15:12:25  matt
** initial revision
**
*/
